﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using Sofire.Expressions;
using System.Xml.Linq;

namespace Sofire.Data.XSql
{
    internal class XSqlDocument : IXSqlDocument
    {
        public XSqlDocument(QueryEngineBase queryEngine)
        {
            this._Engine = queryEngine;
            this._XSqls = new XSqlCollection(11);
        }

        public IXSqlNode this[string name]
        {
            get { return this._XSqls[name]; }
        }


        IXSqlNodeCollection IXSqlDocument.XSqls
        {
            get { return _XSqls; }
        }

        private QueryEngineBase _Engine;
        public QueryEngineBase Engine
        {
            get
            {
                return this._Engine;
            }
            set
            {
                this._Engine = value;
            }
        }

        private XSqlCollection _XSqls;
        public XSqlCollection XSqls
        {
            get
            {
                return this._XSqls;
            }
        }

        private string _Name;
        public string Name
        {
            set
            {
                this._Name = value;
            }
            get
            {
                return this._Name;
            }
        }

        public ExecuteCommand CreateCommand(string name)
        {
            return CreateCommand(name, null);
        }
        public ExecuteCommand CreateCommand(string name, XSqlParameterCollection parameters)
        {
            var xsqlNode = this._XSqls[name];
            if(xsqlNode == null) throw new XSqlException(null, XSqlError.UnfoundXSql) { AttributeName = name };
            return this.CreateCommand(xsqlNode, parameters);
        }

        public ExecuteCommand CreateCommand(IXSqlNode xsqlNode)
        {
            return CreateCommand(xsqlNode, null);
        }

        public ExecuteCommand CreateCommand(IXSqlNode xsqlNode, XSqlParameterCollection parameters)
        {
            if(xsqlNode == null) throw new ArgumentNullException("xsqlNode");
            if(parameters == null) parameters = new XSqlParameterCollection(0);

            //var s = xsqlNode.Compile(parameters);
            //if(s == null) throw new XSqlException(xsqlNode, XSqlError.InvalidResult);
            //var commandText = TrimRegex.Replace(s.TrimEnd(), string.Empty);
            // return new ExecuteCommand(commandText, CreateParameters(parameters));
            return (xsqlNode as XSqlNode).CreateCommand(parameters);
        }

        const string NodeName = "document";
        private void Load(XmlDocument document)
        {
            var docEle = document.DocumentElement;
            if(docEle.Name != NodeName) throw new NotSupportedException(docEle.InnerXml);
            //var nameAttr = docEle.Attributes[XSqlAttributes.Name];
            //if(nameAttr != null) this._Name = nameAttr.Value;
            this.AnalyzeXmlNodeChildren(null, docEle);
        }

        private XDocument CreateDocument()
        {
            var element = new XElement(NodeName);
            XDocument document = new XDocument(element);
            foreach(XSqlNodeBase node in this._XSqls)
            {
                element.Add(node.CreateXObject());
            }
            return document;
        }

        public void Save(string fileName)
        {
            var document = this.CreateDocument();
            document.Save(fileName);
        }

        public void Save(System.IO.TextWriter writer)
        {
            var document = this.CreateDocument();
            document.Save(writer);
        }

        public void Save(XmlWriter writer)
        {
            var document = this.CreateDocument();
            document.Save(writer);
        }

        public string CreateXml()
        {
            var document = this.CreateDocument();
            return document.ToString();
        }

        public void Load(string path)
        {
            XmlDocument document = new XmlDocument();
            document.Load(path);
            this.Load(document);
        }

        public void LoadXml(string xml)
        {
            XmlDocument document = new XmlDocument();
            document.LoadXml(xml);
            this.Load(document);
        }

        internal void AnalyzeXmlNodeChildren(XSqlNodeBase parent, XmlNode parentXmlNode)
        {
            XSqlNodeBase prev = null;
            foreach(XmlNode childXmlNode in parentXmlNode.ChildNodes)
            {
                var prev2 = Analyze(childXmlNode, parent, prev);
                if(prev2 == null) continue;
                prev = prev2;
            }
        }

        internal XSqlNodeBase AnalyzeElement(XmlNode xmlNode, XSqlNodeBase parent, XSqlNodeBase previous)
        {
            XSqlNodeBase xsqlNode;

            #region Create Xsql Node

            switch(xmlNode.Name)
            {
                case XSqlIncludeNode.NodeName:
                    if(parent == null)
                    {
                        var path = xmlNode.TryGetAttribute("path");
                        if(!System.IO.File.Exists(path)) throw new System.IO.FileNotFoundException(xmlNode.InnerXml, path);
                        this.Load(path);
                        return null;
                    }

                    return new XSqlIncludeNode();

                case XSqlParameterNode.NodeName:
                    return new XSqlParameterNode();

                case XSqlNode.NodeName:
                    xsqlNode = new XSqlNode();
                    break;

                case XSqlProcedureNode.NodeName:
                    xsqlNode = new XSqlProcedureNode();
                    break;
                case XSqlExpressionNode.NodeName:
                    return new XSqlExpressionNode();

                case XSqlTrimNode.NodeName:
                    xsqlNode = new XSqlTrimNode();
                    break;

                case XSqlIfNode.NodeName:
                    xsqlNode = new XSqlIfNode();
                    break;
                case XSqlElseNode.NodeName:
                    xsqlNode = new XSqlElseNode();
                    break;

                case XSqlSwitchNode.NodeName:
                    xsqlNode = new XSqlSwitchNode();
                    break;
                case XSqlCaseNode.NodeName:
                    xsqlNode = new XSqlCaseNode();
                    break;
                case XSqlDefaultNode.NodeName:
                    xsqlNode = new XSqlDefaultNode();
                    break;

                case XSqlForeachNode.NodeName:
                    xsqlNode = new XSqlForeachNode();
                    break;

                case XSqlAttributes.Test:
                    //var testParent = (parent as XSqlTestNodeBase);
                    //if(testParent == null) goto default;
                    //testParent.Test = xmlNode.InnerText;
                    return null;

                case XSqlParametersNode.NodeName:
                    xsqlNode = new XSqlParametersNode();
                    break;
                default:
                    throw new NotSupportedException(xmlNode.OuterXml);
            }

            #endregion

            this.AnalyzeXmlNodeChildren(xsqlNode, xmlNode);

            return xsqlNode;
        }

        internal XSqlNodeBase Analyze(XmlNode xmlNode, XSqlNodeBase parent, XSqlNodeBase previous)
        {
            XSqlNodeBase xsqlNode;
            switch(xmlNode.NodeType)
            {
                case XmlNodeType.Element:
                    xsqlNode = this.AnalyzeElement(xmlNode, parent, previous);
                    if(xsqlNode == null) goto default;
                    break;
                case XmlNodeType.CDATA:
                case XmlNodeType.Text:
                    xsqlNode = new XSqlTextNode();
                    break;
                default:
                    return null;
            }

            #region Init xsql comm properties

            xsqlNode.Document = this;
            if(parent != null)
            {
                xsqlNode.Parent = parent;
                parent.Children.Add(xsqlNode);
            }

            if(previous != null)
            {
                xsqlNode.Previous = previous;
                previous.Next = xsqlNode;
            }


            IXSql ixsqlNode = xsqlNode;
            ixsqlNode.Load(xmlNode);

            #endregion
            if(parent == null)
            {
                switch(ixsqlNode.Type)
                {
                    case XSqlType.Procedure:
                    case XSqlType.XSql:
                        this._XSqls.Add(xsqlNode as XSqlNode);
                        break;
                }
            }
            return xsqlNode;
        }

        internal Expression CreateExpression(string expression)
        {
            var exp = new Expression(expression, EvaluateOptions.IgnoreCase);
            if(XSqlManager.Functions.Count > 0)
            {
                exp.Functions = XSqlManager.Functions;
            }
            return exp;
        }


    }
}
